package com.sopaths.utility.log4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.RollingFileAppender;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.spi.LoggingEvent;

public class ThreadIsolationRollingFileAppender extends AppenderSkeleton {

	private Map<String, RollingFileAppender> appenders = new ConcurrentHashMap<>();

	protected String fileName;

	protected String encoding;

	protected long maxFileSize = 10 * 1024 * 1024;

	protected int maxBackupIndex = 1;

	protected boolean fileAppend = true;

	@Override
	public void close() {
		for (AppenderSkeleton appender : appenders.values()) {
			try {
				appender.close();
			} catch (Exception ex) {
				ex.printStackTrace(System.err);
			}
		}
	}

	@Override
	public boolean requiresLayout() {
		return true;
	}

	@Override
	protected void append(LoggingEvent event) {
		try {
			String threadName = event.getThreadName();
			RollingFileAppender appender = null;
			synchronized (appenders) {
				if (!appenders.containsKey(threadName)) {
					StringBuilder fileNameBuilder = new StringBuilder()
							.append(fileName).append("[").append(threadName)
							.append("]").append(".log");
					appender = new RollingFileAppender(layout, fileNameBuilder.toString(), fileAppend);
					appender.setEncoding(encoding);
					appender.setMaximumFileSize(maxFileSize);
					appender.setMaxBackupIndex(maxBackupIndex);
					appender.setAppend(fileAppend);
					appender.setLayout(layout);
					appenders.put(threadName, appender);
				} else {
					appender = appenders.get(threadName);
				}
			}
			appender.append(event);
		} catch (Exception ex) {
			ex.printStackTrace(System.err);
		}
	}

	/**
	 * Set the maximum size that the output file is allowed to reach before
	 * being rolled over to backup files.
	 *
	 * <p>
	 * This method is equivalent to {@link #setMaxFileSize} except that it is
	 * required for differentiating the setter taking a <code>long</code>
	 * argument from the setter taking a <code>String</code> argument by the
	 * JavaBeans {@link java.beans.Introspector Introspector}.
	 *
	 * @see #setMaxFileSize(String)
	 */
	public void setMaximumFileSize(long maxFileSize) {
		this.maxFileSize = maxFileSize;
	}

	/**
	 * Set the maximum size that the output file is allowed to reach before
	 * being rolled over to backup files.
	 *
	 * <p>
	 * In configuration files, the <b>MaxFileSize</b> option takes an long
	 * integer in the range 0 - 2^63. You can specify the value with the
	 * suffixes "KB", "MB" or "GB" so that the integer is interpreted being
	 * expressed respectively in kilobytes, megabytes or gigabytes. For example,
	 * the value "10KB" will be interpreted as 10240.
	 */
	public void setMaxFileSize(String value) {
		maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
	}

	/**
	 * The <b>File</b> property takes a string value which should be the name of
	 * the file to append to.
	 *
	 * <p>
	 * <font color="#DD0044"><b>Note that the special values "System.out" or
	 * "System.err" are no longer honored.</b></font>
	 *
	 * <p>
	 * Note: Actual opening of the file is made when {@link #activateOptions} is
	 * called, not when the options are set.
	 */
	public void setFile(String file) {
		this.fileName = file;
	}

	/**
	 * Set the maximum number of backup files to keep around.
	 *
	 * <p>
	 * The <b>MaxBackupIndex</b> option determines how many backup files are
	 * kept before the oldest is erased. This option takes a positive integer
	 * value. If set to zero, then there will be no backup files and the log
	 * file will be truncated when it reaches <code>MaxFileSize</code>.
	 */
	public void setMaxBackupIndex(int maxBackups) {
		this.maxBackupIndex = maxBackups;
	}

	/**
	 * The <b>Append</b> option takes a boolean value. It is set to
	 * <code>true</code> by default. If true, then <code>File</code> will be
	 * opened in append mode by {@link #setFile setFile} (see above). Otherwise,
	 * {@link #setFile setFile} will open <code>File</code> in truncate mode.
	 *
	 * <p>
	 * Note: Actual opening of the file is made when {@link #activateOptions} is
	 * called, not when the options are set.
	 */
	public void setAppend(boolean flag) {
		fileAppend = flag;
	}

	public void setEncoding(String value) {
		encoding = value;
	}
}
